//-------------------------------------------------------------------
//	Copyright (c) 2012-2014 Zhirnov Andrey
//	This file is part of the "UXGEN-ES" project.
//	See copyright notice in "Engine.h".
//-------------------------------------------------------------------

#define PLATFORM_SDL

#include "Utils/Shader/ShaderConfig/ShaderUtils.h"

namespace Engine
{
namespace Utils
{

	static void ShowShaderLog (GLuint shader, GLenum type)
	{
		GLint	i_logsize = 0;
		glGetShaderiv( shader, GL_INFO_LOG_LENGTH, &i_logsize );

		if ( i_logsize > 3 )
		{
			const char *	s_aPrefixes[] = { "Unknown shader type ",
												"Vertex shader ",
												"Fragment shader " };

			const char *	p_prefix = s_aPrefixes[0];

			string	log;

			switch ( type )
			{
				case GL_VERTEX_SHADER	 :	p_prefix = s_aPrefixes[1];	break;
				case GL_FRAGMENT_SHADER :	p_prefix = s_aPrefixes[2];	break;
			};

			string	s_info;
			s_info.Reserve( i_logsize + 1 );

			glGetShaderInfoLog( shader, i_logsize, null, s_info.ptr() );

			s_info.SetLength( i_logsize );

			log << p_prefix << "compilation message:\n" << s_info;

			WARNING( log.cstr() );
		}
	}


	static void ShowProgramLog (GLuint prog)
	{
		GLint	i_res = 0;
		glGetProgramiv( prog, GL_LINK_STATUS, &i_res );
		bool	linked = (i_res == GL_TRUE);

		GLint	i_logsize	= 0;
		glGetProgramiv( prog, GL_INFO_LOG_LENGTH, &i_logsize );

		if ( i_logsize > 3 )
		{
			string	s_info;
			s_info.Reserve( i_logsize + 1 );

			glGetProgramInfoLog( prog, i_logsize, nullptr, s_info.ptr() );

			s_info.SetLength( i_logsize );

			string	log;
			
			if ( linked )	log << "program linking message:\n";
			else			log << "program linking error:\n";

			log << s_info;

			WARNING( log.cstr() );
		}
	}


	static void ShowValidationLog (GLuint prog)
	{
		GLint	i_res		= 0,
				i_logsize	= 0;
		bool	b_valid		= false;

		glGetProgramiv( prog, GL_VALIDATE_STATUS, &i_res );
		b_valid = (i_res == GL_TRUE);
		
		glGetProgramiv( prog, GL_INFO_LOG_LENGTH, &i_logsize );

		if ( i_logsize > 0 )
		{
			string	s_info;
			s_info.Reserve( i_logsize + 1 );

			glGetProgramInfoLog( prog, i_logsize, null, s_info.ptr() );

			s_info.SetLength( i_logsize );
			
			string log;

			if ( b_valid )	log << "program validation message:\n";
			else			log << "program validation error:\n";
			
			log << s_info;

			WARNING( log.cstr() );
		}
	}

	
	// Validate
	bool ShaderUtils::Validate (const char *fs, const char *vs,
								const ShaderAttrib *attribs, int attribsCount,
								const char ** uniforms, int uniformsCount)
	{
		if ( not _IsInitialized() )
		{
			_Destroy();
			CHECK_ERR( _Init() );
		}

		CHECK_ERR( ::SDL_GL_MakeCurrent( (SDL_Window *)_window, (SDL_GLContext)_renderContext ) == 0 );
		_platform_::UX_GLWINDOW_SET_CONTEXT( &_glContext );
		

		GLuint	frag_sh = glCreateShader( GL_FRAGMENT_SHADER );
		GLuint	vert_sh = glCreateShader( GL_VERTEX_SHADER );
		GLuint	prog	= glCreateProgram();

		
		const string	fs_src	= string("#version 400\n"
										 "#define varying in\n"
										 "out vec4 gl_FragColor;\n") << fs;

		const string	vs_src	= string("#version 400\n"
										 "#define varying out\n"
										 "#define attribute in\n") << vs;

		const char *	fs_ptr	= fs_src.cstr();
		const char *	vs_ptr	= vs_src.cstr();

		GLint	i_res = 0;
		bool	vs_compiled,
				fs_compiled,
				prog_linked,
				prog_valid;

		glShaderSource( frag_sh, 1, &fs_ptr, 0 );
		glCompileShader( frag_sh );
		glGetShaderiv( frag_sh, GL_COMPILE_STATUS,  &i_res );
		fs_compiled = i_res == GL_TRUE;

		glShaderSource( vert_sh, 1, &vs_ptr, 0 );
		glCompileShader( vert_sh );
		glGetShaderiv( vert_sh, GL_COMPILE_STATUS,  &i_res );
		vs_compiled = i_res == GL_TRUE;

		if ( not fs_compiled )
			ShowShaderLog( frag_sh, GL_FRAGMENT_SHADER );
		
		if ( not vs_compiled )
			ShowShaderLog( vert_sh, GL_VERTEX_SHADER );

		glAttachShader( prog, frag_sh );
		glAttachShader( prog, vert_sh );
	
		for (int i = 0; i < attribsCount; ++i)
		{
			glBindAttribLocation( prog, attribs[i].index, attribs[i].name );
		}

		glLinkProgram( prog );
		glGetProgramiv( prog, GL_LINK_STATUS, &i_res );
		prog_linked = i_res == GL_TRUE;

		if ( not prog_linked )
			ShowProgramLog( prog );

		glValidateProgram( prog );

		glGetProgramiv( prog, GL_VALIDATE_STATUS, &i_res );
		prog_valid = i_res == GL_TRUE;

		if ( not prog_valid )
			ShowValidationLog( prog );

		bool	res = prog_linked and prog_valid;

		ASSERT( res );


		// check attribs //
		/*for (int i = 0; i < attribsCount; ++i)
		{
			GLint	loc = -1;
			
			loc = glGetAttribLocation( prog.Id(), attribs[i].name );
			ASSERT_EXT( loc != -1, (string("attribute \"") << attribs[i].name << "\" not defined!").cstr() );
		}*/


		// check uniforms //
		for (int i = 0; i < uniformsCount; ++i)
		{
			GLint	loc = -1;

			loc = glGetUniformLocation( prog, uniforms[i] );
			ASSERT_EXT( loc != -1, (string("uniform \"") << uniforms[i] << "\" not defined!").cstr() );
		}

		CHECK_ERR( ::SDL_GL_MakeCurrent( (SDL_Window *)_window, null ) == 0 );
		_platform_::UX_GLWINDOW_SET_CONTEXT( null );

		return res;
	}

	
	bool ShaderUtils::_Init ()
	{
		CHECK_ERR( ::SDL_VideoInit( null ) == 0 );

		uint	flags = int(SDL_WINDOW_OPENGL) | int(SDL_WINDOW_SHOWN) | int(SDL_WINDOW_BORDERLESS);

		CHECK_ERR( (_window = ::SDL_CreateWindow( "", 0, 0, 1, 1, flags )) != null );
		
		::SDL_HideWindow( (SDL_Window *)_window );
		
		::SDL_GL_SetAttribute( SDL_GL_RED_SIZE,					8 );
		::SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE,				8 );
		::SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE,				8 );
		::SDL_GL_SetAttribute( SDL_GL_ALPHA_SIZE,				0 );
		::SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER,				1 );
		::SDL_GL_SetAttribute( SDL_GL_BUFFER_SIZE,				0 );
		::SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE,				16 );
		::SDL_GL_SetAttribute( SDL_GL_STENCIL_SIZE,				0 );
		::SDL_GL_SetAttribute( SDL_GL_ACCELERATED_VISUAL,		1 );
		
		int	maj_ver = 4,
			min_ver = 3;

		::SDL_GL_SetAttribute( SDL_GL_CONTEXT_MAJOR_VERSION,	maj_ver );
		::SDL_GL_SetAttribute( SDL_GL_CONTEXT_MINOR_VERSION,	min_ver );

		while ( (_renderContext = ::SDL_GL_CreateContext( (SDL_Window *)_window )) == null )
		{
			if ( min_ver == 0 )	{
				if ( maj_ver <= 1 ) {
					RETURN_ERR( "can't create render context!" );
				}
				--maj_ver;
				min_ver = 5;
			}
			else {
				--min_ver;
			}

			::SDL_GL_SetAttribute( SDL_GL_CONTEXT_MAJOR_VERSION,	maj_ver );
			::SDL_GL_SetAttribute( SDL_GL_CONTEXT_MINOR_VERSION,	min_ver );
		}


		CHECK_ERR( ::SDL_GL_MakeCurrent( (SDL_Window *)_window, (SDL_GLContext)_renderContext ) == 0 );

		maj_ver = min_ver = 0;
		::SDL_GL_GetAttribute( SDL_GL_CONTEXT_MAJOR_VERSION, &maj_ver );
		::SDL_GL_GetAttribute( SDL_GL_CONTEXT_MINOR_VERSION, &min_ver );

		_version = maj_ver * 100 + min_ver * 10;

		LOG( (string("created render context with version: ") << _version).cstr(), e_log_msg::INFO );

		_glContext.Init();
		_glContext.ClearLog();
		
		::SDL_GL_MakeCurrent( (SDL_Window *)_window, null );
		return true;
	}


	void ShaderUtils::_Destroy ()
	{
		if ( _renderContext != null )
		{
			::SDL_GL_DeleteContext( (SDL_GLContext)_renderContext );
			_renderContext = null;
		}

		::SDL_DestroyWindow( (SDL_Window *)_window );

		::SDL_VideoQuit();
	}
	

	bool ShaderUtils::_IsInitialized () const
	{
		return	_window != null and
				_renderContext != null;
	}


}	// Utils
}	// Engine
